package com.atguigu.tingshu.album.service.impl;

import com.atguigu.tingshu.album.mapper.AlbumAttributeValueMapper;
import com.atguigu.tingshu.album.mapper.AlbumInfoMapper;
import com.atguigu.tingshu.album.mapper.AlbumStatMapper;
import com.atguigu.tingshu.album.service.AlbumAttributeValueService;
import com.atguigu.tingshu.album.service.AlbumInfoService;
import com.atguigu.tingshu.common.cache.TsCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.AlbumStat;
import com.atguigu.tingshu.query.album.AlbumInfoQuery;
import com.atguigu.tingshu.vo.album.AlbumAttributeValueVo;
import com.atguigu.tingshu.vo.album.AlbumInfoVo;
import com.atguigu.tingshu.vo.album.AlbumListVo;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;


import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {

	@Autowired
	private AlbumInfoMapper albumInfoMapper;
	@Autowired
	private AlbumAttributeValueMapper albumAttributeValueMapper;
	@Autowired
	private AlbumStatMapper albumStatMapper;
	@Autowired
	private AlbumAttributeValueService albumAttributeValueService;
	@Autowired
	private RabbitService rabbitService;
	@Autowired
	private RedisTemplate redisTemplate;
	@Autowired
	private RedissonClient redissonClient;


	@Override
	//三张表,所以要开启事务
	@Transactional(rollbackFor = Exception.class)
	public Boolean saveAlbumInfo(AlbumInfoVo albumInfoVo,Long userId) {
		try {
		/*
		album_info//专辑信息
		album_stat//专辑统计
		album_attribute_value//专辑属性值关联表
		*/
			//1.先保存专辑信息表
			AlbumInfo albumInfo=new AlbumInfo();
			//属性赋值:前提条件是属性名必须一致才能进行复制
			//可能进行深拷贝,也可能进行浅拷贝
			BeanUtils.copyProperties(albumInfoVo,albumInfo);
			//前端传递的数据是否都要保存到数据表或者保存数据表中的数据前端都传递了嘛?-->校验
			//将Json字符串和album_info表中的字段进行对比
		/*
		user_id没有,需要知道当前专辑是谁创建的?获取当前登录用户id
		免费试听集数tracks_for_free(付费专辑先让用户试听)
		status
		*/
			albumInfo.setUserId(userId);
			//设置试听集数:有个前提必须是付费专辑
			if (!SystemConstant.ALBUM_PAY_TYPE_FREE.equals(albumInfoVo.getPayType())){
				//付费:设置集数
				//页面正常来讲,在制作专辑的时候,应该有一个框:免费集数,它应该在付费下边,通过这个页面将免费集数传过来
				albumInfo.setTracksForFree(5);
			}
			//设置审核状态:这里有点强硬
			//正常流程:发送消息给管理平台->站内信息(提示信息),审核通过:监听消息完成审核状态赋值
			albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);

			//表示执行insert()方法保存数据
			albumInfoMapper.insert(albumInfo);

			//2.album_attribute_value保存标签
			/*List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
			if (!CollectionUtils.isEmpty(albumAttributeValueVoList)){
				//循环遍历赋值
				for (AlbumAttributeValueVo albumAttributeValueVo : albumAttributeValueVoList) {
					//本质是给album_attribute_value这个表赋值
					//所以先创建对象
					AlbumAttributeValue albumAttributeValue = new AlbumAttributeValue();
					//用mapper保存数据
					albumAttributeValue.setAlbumId(albumInfo.getId());//前端没有传递只能存albumInfo中获取
					albumAttributeValue.setAttributeId(albumAttributeValueVo.getAttributeId());
					albumAttributeValue.setValueId(albumAttributeValueVo.getValueId());
					//本质:执行多次insert into album_attribute_value values();
					albumAttributeValueMapper.insert(albumAttributeValue);

				}
				//优化进行批量保存this.saveBatch(),这里的this代表的是AlbumInfoService,所以要想批量保存还需要准备AlbumAttributeValueService TODO
			}*/
			List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
			if (!CollectionUtils.isEmpty(albumAttributeValueVoList)){
				// 循环遍历
				List<AlbumAttributeValue> attributeValueList = albumAttributeValueVoList.stream().map(albumAttributeValueVo -> {
					// 创建一个实体对象
					AlbumAttributeValue albumAttributeValue = new AlbumAttributeValue();
					BeanUtils.copyProperties(albumAttributeValueVo, albumAttributeValue);
					albumAttributeValue.setAlbumId(albumInfo.getId());
					return albumAttributeValue;
				}).collect(Collectors.toList());
				// 批量插入
				this.albumAttributeValueService.saveBatch(attributeValueList);
			}
			//3.album_stat保存专辑统计表
			saveAlbumStat(albumInfo.getId(),SystemConstant.ALBUM_STAT_PLAY);//播放量
			saveAlbumStat(albumInfo.getId(),SystemConstant.ALBUM_STAT_SUBSCRIBE);//订阅量
			saveAlbumStat(albumInfo.getId(),SystemConstant.ALBUM_STAT_BROWSE);//购买量
			saveAlbumStat(albumInfo.getId(),SystemConstant.ALBUM_STAT_COMMENT);//评论量

			//判断当前专辑是否公开is_open:1
			if ("1".equals(albumInfo.getIsOpen())){
				//调用上架方法
				rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM,MqConst.ROUTING_ALBUM_UPPER,albumInfo.getId());
				RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
				bloomFilter.add(albumInfo.getId());
			}
		} catch (BeansException e) {
			//开发中都需要使用这种方式进行日志输出,不能直接使用e.printStackTrace();,因为这类使用会占用资源
			log.error(e.getMessage(),e);
			throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
			//不可达代码
			//return false;
		}
		return true;
	}
	public void saveAlbumStat(Long albumId, String statPlay) {
		//创建对象
		AlbumStat albumStat = new AlbumStat();
		albumStat.setAlbumId(albumId);
		albumStat.setStatNum(new Random().nextInt(100000));
		albumStat.setStatType(statPlay);//有类型了,但是类型对应的值没有,所以应该在上边加一行
		albumStatMapper.insert(albumStat);
	}

	//分页查询用户专辑列表
	@Override
	public IPage<AlbumListVo> findUserAlbumPage(Page<AlbumListVo> page, AlbumInfoQuery albumInfoQuery) {
		//需要通过两张表进行查询
		return albumInfoMapper.selectUserAlbumPage(page,albumInfoQuery);
	}

	//根据专辑Id查看专辑信息
	@Override
	@TsCache(keyPrefix = "album:")
	public AlbumInfo getAlbumInfo(Long albumId) {
		//因为album不重复,所以和前缀拼接起来之后也是唯一的,若还有一个方法的参数也是album此时需要通过修改前缀来保证唯一性
		return getAlbumInfoDB(albumId);

		/*//  定义String 数据类型；
		String dataKey = RedisConstant.ALBUM_INFO_PREFIX + albumId;
		try {
			//  根据缓存的key获取数据;
			AlbumInfo albumInfo = (AlbumInfo) this.redisTemplate.opsForValue().get(dataKey);
			//  判断缓存中是否存在数据;
			if (null == albumInfo){
				System.out.println("缓存没有数据");
				//  不存在; 查询数据库;
				//  加锁;
				String lockKey = RedisConstant.ALBUM_LOCK_SUFFIX + albumId;
				RLock lock = redissonClient.getLock(lockKey);
				lock.lock();
				try {
					//查询数据库之前,再判断一次缓存
					albumInfo = (AlbumInfo) this.redisTemplate.opsForValue().get(dataKey);
					if (null != albumInfo){
						//说明在第二个线程进来之前,有人已经把数据放进了缓存中
						return albumInfo;
					}
					//  业务逻辑;
					albumInfo = getAlbumInfoDB(albumId);
					//判断数据是否为空
					if (null==albumInfo){
						//数据库缓存均没有数据,所以防止穿透,设置一个空值
						this.redisTemplate.opsForValue().set(dataKey,new AlbumInfo(),RedisConstant.ALBUM_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
						return new AlbumInfo();
					}
					//数据库中有数据,需要放到缓存中
					this.redisTemplate.opsForValue().set(dataKey,albumInfo,RedisConstant.ALBUM_TIMEOUT, TimeUnit.SECONDS);
					//返回数据
					return albumInfo;
				} catch (Exception e) {
					//  异常信息;
					log.error(e.getMessage(),e);
				} finally {
					//  解锁：
					lock.unlock();
				}
			}else{
				//缓存有数据
				System.out.println("缓存有数据");
				return albumInfo;
			}
		} catch (Exception e) {
			log.error(e.getMessage(),e);
		}
		//数据库兜底
		return getAlbumInfoDB(albumId);*/
	}

	@Nullable
	private  AlbumInfo getAlbumInfoDB(Long albumId) {
		//因为需要给专辑标签赋值albumAttributeValueVoList
		//查询专辑信息表
		//vo和不带vo的字段差不多就直接写albumInfo这样就可以直接返回如果用vo还得赋值,避免重复属性拷贝
		AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
		//防止空指针
		if (null != albumInfo){
			//根据专辑id获取到标签数据:
			//select * from album_attribute_value where album_id=1616
			//Wapper构建查询条件的
			LambdaQueryWrapper<AlbumAttributeValue> wrapper = new LambdaQueryWrapper<>();
			wrapper.eq(AlbumAttributeValue::getAlbumId, albumId);//两行代码等价于where album_id=1616
			List<AlbumAttributeValue> albumAttributeValueList = albumAttributeValueMapper.selectList(wrapper);
			albumInfo.setAlbumAttributeValueVoList(albumAttributeValueList);
		}
		//返回数据
		return albumInfo;
	}

	//根据专辑Id修改专辑信息
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void updateAlbumInfo(Long albumId, AlbumInfoVo albumInfoVo) {
		//修改 update album_info set title =?where id=?
		AlbumInfo albumInfo=new AlbumInfo();
		//属性拷贝:第一个参数是源,第二个参数是目标
		BeanUtils.copyProperties(albumInfoVo,albumInfo);
		//因为albumInfoVo没id
		albumInfo.setId(albumId);
		albumInfoMapper.updateById(albumInfo);

		/*
		如果更新album_attribute_value,需要先删除再新增
		因为更新标签时直接点击,并不会触发请求(并不知道目前改了哪些数据)
		当点击事件之后获取不到更改哪些数据,此时就需要进行先删除,再新增从而进行更新
		*/
		//更新album_attribute_value,由于修改值的时间未触发控制器,所以不能实时记录修改的信息,因此要先删除数据,再更新数据
		//删除原有标签数据 delete from album_attribute_value where album_id=?----物理删除  isDeleted表示的是逻辑删除 TableLogic
		albumAttributeValueMapper.delete(
				new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId,albumId));
		//新增,从前端传入的 AlbumInfoVo 对象中获取新的标签列表
		List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
		if (!CollectionUtils.isEmpty(albumAttributeValueVoList)){
			//循环遍历修改数据
			for (AlbumAttributeValueVo albumAttributeValueVo : albumAttributeValueVoList) {
				//创建修改的实体对象
				AlbumAttributeValue albumAttributeValue = new AlbumAttributeValue();
				//属性拷贝
				BeanUtils.copyProperties(albumAttributeValueVo,albumAttributeValue);
				albumAttributeValue.setAlbumId(albumId);
				//这里需要优化为批量处理
				//insert into album_attribute_value values(?,?,?,?,?) values(?,?,?,?,?) values(?,?,?,?,?)
				albumAttributeValueMapper.insert(albumAttributeValue);

			}
		}
		//判断当前专辑是否公开is_open:1
		if ("1".equals(albumInfo.getIsOpen())){
			//调用上架方法
			rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM,MqConst.ROUTING_ALBUM_UPPER,albumInfo.getId());
			//将专辑id添加到布隆过滤器
			//为啥添加专辑id?而不是添加标题等等---添加布隆过滤器的核心是:看用户具体根据哪个条件要存储
			//缓存穿透:用户在访问这个数据的时候,缓存没有,数据库也没有
			RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
			bloomFilter.add(albumInfo.getId());
		}else {
			//调用下架方法
			rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM,MqConst.ROUTING_ALBUM_LOWER,albumInfo.getId());
		}
	}

	//删除专辑,加事务
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void removeAlbumInfo(Long albumId) {
		//删除album_info
		albumInfoMapper.deleteById(albumId);
		//删除album_attribute_value
		albumAttributeValueMapper.delete(
				new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId,albumId));
		//删除统计信息album_stat
		albumStatMapper.delete(new LambdaQueryWrapper<AlbumStat>().eq(AlbumStat::getAlbumId,albumId));//第一个参数要比较的字段,第二个参数要比较的字段的具体值
		//删除关联:是否删除声音?因为声音在专辑列表下--看业务需求

	}

	//获取用户所有专辑列表
	@Override
	public List<AlbumInfo> findUserAllAlbumList(Long userId) {
		//SELECT * FROM album_info WHERE user_id = ? AND is_deleted = 0
		//构建查询条件
		LambdaQueryWrapper<AlbumInfo> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(AlbumInfo::getUserId,userId);
		//数据库查询默认升序,我们想先查到最新的数据
		wrapper.orderByDesc(AlbumInfo::getId);
		//数据太多,先查100条
		//这里出去说的时候应该说软编码,都是从前端页面动态传递的
		Page<AlbumInfo> albumInfoPage=new Page<>(1,100);
		//List<AlbumInfo> albumInfoList = albumInfoMapper.selectList(wrapper);
		Page<AlbumInfo> infoPage=albumInfoMapper.selectPage(albumInfoPage,wrapper);
		//返回专辑列表
		return infoPage.getRecords();//获取分页查询结果中的数据列表部分
	}

	/**
	 * 获取专辑统计信息
	 * @param albumId
	 * @return
	 */
	@Override
	@TsCache(keyPrefix = "statVo:")
	public AlbumStatVo getAlbumStatVo(Long albumId) {
		//调用mapper层方法
		return albumInfoMapper.selectAlbumStatVo(albumId);
	}
}
